﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using System.ComponentModel;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;

namespace Jemgine.Data
{
        [Jemgine.Engine.Category("Primitive"), Jemgine.Engine.Component]
	public class Line : EntityElement, EditorDrawable, TexturedObject, 
        Translatable, Rotatable, Scalable, ReferenceTarget, HasVerticies, Engine.Renderer.ICastShadow, Engine.Renderer.IDrawable
	{
        public enum TextureMappingStyle
        {
            Horizontal,
            HorizontalFlipped,
            Vertical,
            VerticalFlipped,
        }

        public int BoneID { get; set; }
        [ContentSerializerIgnore]
        public Bone BoneReference { get; set; }

        private TextureMappingStyle _mappingStyle = TextureMappingStyle.Horizontal;
        public TextureMappingStyle MappingStyle { get { return _mappingStyle; } 
            set { _mappingStyle = value; ClearDecomposedCache(); } }
                
        public Engine.GeometryType Type { get; set; }

        [ContentSerializer(Optional = true)]
        public Engine.Renderer.ShadowType ShadowType { get; set; }

        [Microsoft.Xna.Framework.Content.ContentSerializerIgnore]
        public int ReferenceID { get; set; }

        private float _width = 1.0f;
        public float Width { get { return _width; } set { _width = value; ClearDecomposedCache(); } }

        private Vector2 _textureScale = Vector2.One;
        public Vector2 TextureScale { get { return _textureScale; } set { _textureScale = value; ClearDecomposedCache(); } }

        public bool RoundEnds { get; set; }

        Vector2 ReferenceTarget.GetWorldPosition()
        {
            if (Owner == null) return Vector2.Zero;
            else return Owner.Position + Data[1].Position;
        }

        [Microsoft.Xna.Framework.Content.ContentSerializerIgnore]
        public EntityRecord Owner { get; private set; }

        public void SetOwner(EntityRecord E)
        {
            Owner = E;
        }

		public Line(Line p)
		{
            for (int i = 0; i < 2; ++i)
                Data[i] = new Vertex(p.Data[i].Position);
			Texture = p.Texture;
            Width = p.Width;
            TextureScale = p.TextureScale;
            _mappingStyle = p._mappingStyle;
            Type = p.Type;
            RoundEnds = p.RoundEnds;
            BoneID = p.BoneID;
            ShadowType = p.ShadowType;

            Decomposed = null;
		}

		public Line()
		{
            for (int i = 0; i < 2; ++i)
                Data[i] = new Vertex();
            Width = 0.4f;
            TextureScale = Vector2.One;
            Type = Engine.GeometryType.Both;
            RoundEnds = true;
            ShadowType = Engine.Renderer.ShadowType.Solid;
            
            Decomposed = null;
		}

        [Microsoft.Xna.Framework.Content.ContentSerializerIgnore]
        public VertexPositionTexture[] Decomposed = null;

        public void UpdateDecomposedCache()
        {
            Decomposed = new VertexPositionTexture[4];

            Vector2 Tangent = Jemgine.Math.Vector.Perpendicular(this[1].Position - this[0].Position);
            Tangent.Normalize();
            

            for (int i = 0; i <= 1; ++i)
            {
                Vector2 Position = this[i].Position;

                Decomposed[i * 2].Position = new Vector3(Position + (Tangent * Width / 2.0f), 0);
                Decomposed[(i * 2) + 1].Position = new Vector3(Position - (Tangent * Width / 2.0f), 0);

                switch (MappingStyle)
                {
                    case TextureMappingStyle.Horizontal:
                        Decomposed[i * 2].TextureCoordinate = new Vector2(i, 0) * TextureScale;
                        Decomposed[(i * 2) + 1].TextureCoordinate = new Vector2(i, 1) * TextureScale;
                        break;
                    case TextureMappingStyle.HorizontalFlipped:
                        Decomposed[i * 2].TextureCoordinate = new Vector2(1.0f - i, 0) * TextureScale;
                        Decomposed[(i * 2) + 1].TextureCoordinate = new Vector2(1.0f - i, 1) * TextureScale;
                        break;
                    case TextureMappingStyle.Vertical:
                        Decomposed[i * 2].TextureCoordinate = new Vector2(0, i) * TextureScale;
                        Decomposed[(i * 2) + 1].TextureCoordinate = new Vector2(1, i) * TextureScale;
                        break;
                    case TextureMappingStyle.VerticalFlipped:
                        Decomposed[i * 2].TextureCoordinate = new Vector2(0, 1.0f - i) * TextureScale;
                        Decomposed[(i * 2) + 1].TextureCoordinate = new Vector2(1, 1.0f - i) * TextureScale;
                        break;
                }

            }
        }

        public void ClearDecomposedCache()
        {
            Decomposed = null;
        }

        public Vertex[] Data = new Vertex[2];

        [System.ComponentModel.Editor(typeof(TextureEditor), typeof(System.Drawing.Design.UITypeEditor))]
        public String Texture { get; set; }

		public int Count { get { return 2; } }
		public Vertex this[int _index] 
		{ 
			get { return Data[_index]; }
            set { Data[_index] = value; ClearDecomposedCache(); }
		}
        public IVertex GetVertex(int Index) { return this[Index] as IVertex; }

        public Math.AABB GetAABB()
        {
            float MinDimension = System.Math.Max(Width, 1);

            Vector2 Min = this[0].Position;
            Vector2 Max = this[0].Position;

            for (int i = 1; i < 2; ++i)
            {
                if (this[i].Position.Y > Max.Y) Max.Y = this[i].Position.Y;
                if (this[i].Position.Y < Min.Y) Min.Y = this[i].Position.Y;
                if (this[i].Position.X > Max.X) Max.X = this[i].Position.X;
                if (this[i].Position.X < Min.X) Min.X = this[i].Position.X;
            }

            Math.AABB Result = new Math.AABB(Min.X, Min.Y, Max.X - Min.X, Max.Y - Min.Y);

                Result.X -= MinDimension / 2;
                Result.Width += MinDimension;
                Result.Y -= MinDimension / 2;
                Result.Height += MinDimension;

            return Result;
        }

		public bool PointInside(Vector2 point)
		{
            var AABB = GetAABB();
            return Math.AABB.Inside(ref AABB, ref point);
		}

        public Jemgine.Engine.Selection.Types SelectionType()
        {
            return Jemgine.Engine.Selection.Types.EntityElement;
        }

        public EntityElement Clone()
        {
            return new Line(this);
        }

        public void Initialize(Data.EntityRecord Self, Microsoft.Xna.Framework.Content.ContentManager Content)
        {
            UpdateDecomposedCache();
        }

        public void Destroy()
        { }


		#region Translatable Members

        object Translatable.PrepareToTranslate()
        {
            List<Vector2> Positions = new List<Vector2>();
            foreach (var Vertex in Data) Positions.Add(Vertex.Position);
            return Positions;
        }

        void Translatable.ApplyTranslation(object Tag, Vector2 MouseDelta)
        {
            List<Vector2> Positions = Tag as List<Vector2>;
            for (int i = 0; i < Count; ++i)
                Data[i].Position = Positions[i] + MouseDelta;
            ClearDecomposedCache();
        }

        void Translatable.Snap(Snapper Snapper)
        {
            foreach (var Vertex in Data)
                Vertex.Position = Snapper.SnapPosition(Vertex.Position);
        }

        #endregion

        #region Rotatable Members

        object Rotatable.PrepareToRotate()
        {
            List<Vector2> Positions = new List<Vector2>();
            foreach (var Vertex in Data) Positions.Add(Vertex.Position);
            return Positions;
        }

        void Rotatable.ApplyRotation(object Tag, float Rotation)
        {
            List<Vector2> Positions = Tag as List<Vector2>;
            Vector2 Center = Vector2.Zero;
            foreach (var V in Positions) Center += V;
            Center /= Positions.Count;

            Matrix M = Matrix.CreateRotationZ(Rotation);

            for (int I = 0; I < Positions.Count; ++I)
                Data[I].Position = Center + Vector2.Transform(Positions[I] - Center, M);

            ClearDecomposedCache();
        }

        void Rotatable.Snap(Snapper Snapper)
        {
            ( this as Translatable ).Snap(Snapper);
        }

        #endregion

        #region EditorDrawable Members

        public void Draw(int ID, EditorDrawData Context, SelectionType Selected)
        {
            if (Decomposed == null) UpdateDecomposedCache();

            Context.Renderer.Color = EditorDrawData.DrawColor(Selected);

            if (Selected == Jemgine.Data.SelectionType.NotSelected)
                Context.Renderer.DrawElementLine(this, Context.GetTexture(Texture));

            if (Selected == Jemgine.Data.SelectionType.Selected)
                DrawPoints(Context);
            if (Selected == Jemgine.Data.SelectionType.WorkingSet || Selected == Jemgine.Data.SelectionType.Selected)
            {
                var AABB = GetAABB();
                Context.Renderer.DrawBox(AABB.Min, AABB.Max, 0.2f);
            }
        }

        public void DrawPoints(EditorDrawData Context)
        {
            foreach (var point in Data)
                Context.Renderer.DrawPoint(point.Position);
        }

        #endregion


        #region TexturedObject Members

        public void SetTexture(String texture, Texture2D _tex)
        {
            this.Texture = texture;
        }

        #endregion

        #region Scalable Members

        object Scalable.PrepareToScale()
        {
            List<Vector2> Positions = new List<Vector2>();
            foreach (var Vertex in Data) Positions.Add(Vertex.Position);
            return Positions;
        }

        void Scalable.ApplyScale(object Tag, float Delta)
        {
            List<Vector2> Positions = Tag as List<Vector2>;
            Vector2 Center = Vector2.Zero;
            foreach (var V in Positions) Center += V;
            Center /= Positions.Count;

            float MinY = Positions[0].Y;
            float MaxY = Positions[0].Y;
            foreach (var V in Positions)
            {
                if (V.Y > MaxY) MaxY = V.Y;
                if (V.Y < MinY) MinY = V.Y;
            }

            float Range = MaxY - MinY;
            float NewRange = Range + Delta;

            Matrix M = Matrix.CreateScale(NewRange / Range);

            for (int I = 0; I < Positions.Count; ++I)
                Data[I].Position = Center + Vector2.Transform(Positions[I] - Center, M);

            ClearDecomposedCache();        
        }

        void Scalable.Snap(Snapper Snapper)
        {
            ( this as Translatable ).Snap(Snapper);
        }

        #endregion

   
        void Engine.Renderer.ICastShadow.DrawShadow(Engine.Renderer.RenderEffect Effect, 
            Vector2 LightPosition, Matrix Transform, Engine.Renderer.VertexPusher Pusher)
        {
            if (ShadowType == Engine.Renderer.ShadowType.None) return;
            Effect.Texture = Effect.SolidShadowTexture;
            DrawSegmentShadow(Data[0].Position, Data[1].Position, LightPosition, Transform, Pusher, true, Effect);

            //Draw penumbra fins.

        }

        void Engine.Renderer.IDrawable.Draw(
            Engine.Renderer.RenderEffect Effect, 
            Engine.Renderer.VertexPusher Pusher, 
            Engine.EpisodeContentManager Content)
        {
            if ((Type & Engine.GeometryType.Draw) != Engine.GeometryType.Draw) return;

            var Tex = Content.GetTexture(Texture);
            if (Tex == null)
            {
                if (RoundEnds)
                {
                    Effect.Texture = Effect.SolidPointTexture;
                    Effect.PushMatrix(Matrix.CreateTranslation(new Vector3(Data[0].Position, 0.0f)));
                    Effect.PushMatrix(Matrix.CreateScale(Width / 2));
                    Pusher.DrawSprite(false);
                    Effect.PopMatrix();
                    Effect.PopMatrix();
                    Effect.PushMatrix(Matrix.CreateTranslation(new Vector3(Data[1].Position, 0.0f)));
                    Effect.PushMatrix(Matrix.CreateScale(Width / 2));
                    Pusher.DrawSprite(false);
                    Effect.PopMatrix();
                    Effect.PopMatrix();
                    Tex = Effect.LineTexture;
                }
            }

            Effect.Texture = Tex;

            if (Decomposed == null) UpdateDecomposedCache();
            Pusher.DrawTriangleStrip(Decomposed);

            
        }

        public static void DrawSegmentShadow(
            Vector2 P0, Vector2 P1, 
            Vector2 LightPosition,
            Matrix Transform,
            Engine.Renderer.VertexPusher Pusher,
            bool DrawPenumbras,
            Engine.Renderer.RenderEffect Effect,
            bool BacksideOnly = false)
        {
            var v0 = Vector2.Transform(P0, Transform);
            var v1 = Vector2.Transform(P1, Transform);

            if (BacksideOnly)
            {
                if (Jemgine.Math.Vector.CrossZ(Vector2.Normalize(v1 - v0),
                    Vector2.Normalize(LightPosition - v0)) <= 0) return;
            }

            var A = 100.0;

            var F2 = (v1 - v0).LengthSquared();
            var H2 = (LightPosition - v0).LengthSquared();
            var G2 = (LightPosition - v1).LengthSquared();
            var F = (v1 - v0).Length();
            var H = (LightPosition - v0).Length();
            var G = (LightPosition - v1).Length();


            var CosI = (H2 - G2 - F2) / (-2 * G * F);
            var I = System.Math.Acos(CosI);
            var J = (0.5 * System.Math.PI) - I;

            var CosK = (G2 - F2 - H2) / (-2 * F * H);
            var K = System.Math.Acos(CosK);
            var L = (0.5 * System.Math.PI) - K;

            var C = A / System.Math.Cos(J);
            var E = A / System.Math.Cos(L);
            //var B = A / System.Math.Tan(I);
            //var D = A / System.Math.Tan(L);            

            var e0 = v0 - LightPosition;
            var e1 = v1 - LightPosition;

            e0.Normalize();
            e1.Normalize();

            if (DrawPenumbras)
            {
                var lv = v1 - v0;
                lv.Normalize();
                lv *= 50.0f;

                Effect.Texture = Effect.SolidShadowTexture;

                Pusher.VertexBuffer[0].Position = new Vector3(v0, 0.0f);
                Pusher.VertexBuffer[1].Position = new Vector3(v1, 0.0f);
                Pusher.VertexBuffer[2].Position = new Vector3(v0 + (e0 * (float)E)/* + lv*/, 0.0f);
                Pusher.VertexBuffer[3].Position = new Vector3(v1 + (e1 * (float)C)/*- lv*/, 0.0f);


                Pusher.VertexBuffer[0].TextureCoordinate = new Vector2(1, 0);
                Pusher.VertexBuffer[1].TextureCoordinate = new Vector2(0, 0);
                Pusher.VertexBuffer[2].TextureCoordinate = new Vector2(1/*(float)(1 + (D / F))*/, 1);
                Pusher.VertexBuffer[3].TextureCoordinate = new Vector2(0/*(float)(0 - (B / F))*/, 1);

                Pusher.DrawTriangleStrip(Pusher.VertexBuffer);

                Effect.Texture = Effect.PenumbraShadowTexture;

                Pusher.VertexBuffer[0].Position = new Vector3(v0, 0.0f);
                Pusher.VertexBuffer[1].Position = new Vector3(v0 + (e0 * (float)E)/* + lv*/, 0.0f);
                Pusher.VertexBuffer[2].Position = new Vector3(v0 + (e0 * (float)E) - lv, 0.0f);

                Pusher.VertexBuffer[0].TextureCoordinate = new Vector2(0, 1);
                Pusher.VertexBuffer[1].TextureCoordinate = new Vector2(1, 0);
                Pusher.VertexBuffer[2].TextureCoordinate = new Vector2(0, 0);

                Pusher.DrawTriangles(Pusher.VertexBuffer);

                Pusher.VertexBuffer[0].Position = new Vector3(v1, 0.0f);
                Pusher.VertexBuffer[1].Position = new Vector3(v1 + (e1 * (float)C)/* - lv*/, 0.0f);
                Pusher.VertexBuffer[2].Position = new Vector3(v1 + (e1 * (float)C) + lv, 0.0f);

                Pusher.VertexBuffer[0].TextureCoordinate = new Vector2(0, 1);
                Pusher.VertexBuffer[1].TextureCoordinate = new Vector2(1, 0);
                Pusher.VertexBuffer[2].TextureCoordinate = new Vector2(0, 0);

                Pusher.DrawTriangles(Pusher.VertexBuffer);
            }
            else
            {
                Effect.Texture = Effect.SolidShadowTexture;

                Pusher.VertexBuffer[0].Position = new Vector3(v0, 0.0f);
                Pusher.VertexBuffer[1].Position = new Vector3(v1, 0.0f);

                Pusher.VertexBuffer[2].Position = new Vector3(v0 + (e0 * (float)E), 0.0f);
                Pusher.VertexBuffer[3].Position = new Vector3(v1 + (e1 * (float)C), 0.0f);


                Pusher.VertexBuffer[0].TextureCoordinate = new Vector2(1, 0);
                Pusher.VertexBuffer[1].TextureCoordinate = new Vector2(0, 0);
                Pusher.VertexBuffer[2].TextureCoordinate = new Vector2(1/*(float)(1 + (D / F))*/, 50.0f);
                Pusher.VertexBuffer[3].TextureCoordinate = new Vector2(0/*(float)(0 - (B / F))*/, 50.0f);

                Pusher.DrawTriangleStrip(Pusher.VertexBuffer);
            }
        }

    }
}
